package net.gillessed.icarus.engine;


import net.gillessed.icarus.AffineTransform;
import net.gillessed.icarus.FlameModel;
import net.gillessed.icarus.Function;
import net.gillessed.icarus.event.ProgressChangeEvent;
import net.gillessed.icarus.geometry.ColorPoint;
import net.gillessed.icarus.geometry.Point;
import net.gillessed.icarus.swingui.color.ColorProvider;
import net.gillessed.icarus.variation.Variation;

public class FractalEngineThread extends EngineThread {
	
	private static final int BEGINNING_SKIP_COUNT = 200;
	private int colorObject;
	private ColorProvider colorProvider;
	private FlameModel flameModel;
	private FractalEngine fractalEngine;
	
	public FractalEngineThread(FractalEngine engine, EngineThread superSampleThread) {
		super(engine, superSampleThread, FractalEngine.FRACTAL_ALGORITHM);
		localProgress = 0;
		fractalEngine = engine;
		flameModel = fractalEngine.getFlameModel();
		colorProvider = flameModel.getColorProvider();
	}

	@Override
	public void run() {
		fractalEngine.clearArrays();
		fractalAlgorithm();
		fractalEngine.fireProgressChangeEvent(new ProgressChangeEvent(this, 100, true, false));
	}
	public void fractalAlgorithm() {
		double x;
		double y;
		double ptotal;
		double rand;
		
		colorObject = colorProvider.getRandomColorObject();
		ptotal = 0;
		x = Math.random() * 2 - 1;
		y = Math.random() * 2 - 1;
		for(Function v : flameModel.getFunctions()) {
			ptotal += v.getProbability();
		}
		for(Function v : flameModel.getFunctions()) {
			v.setNormalizedProbability(v.getProbability() / ptotal);
		}
		for(int i = 0; i < BEGINNING_SKIP_COUNT; i++)
		{
			rand = Math.random();
			Point p = f(flameModel, rand, x, y);
			x = p.getX();
			y = p.getY();
			augmentProgress();
		}
		for(int i = 0; i < (int)(Math.pow(10,flameModel.getQuality())); i++)
		{
			rand = Math.random();
			ColorPoint p = f(flameModel, rand, x, y);
			x = p.getX();
			y = p.getY();

			int cx = fractalEngine.getViewRectangle().changeX(x, fractalEngine.getPixelWidth());
			int cy = fractalEngine.getViewRectangle().changeY(y, fractalEngine.getPixelHeight());
			if(cx >= 0 && cx < fractalEngine.getPixelWidth() && cy >= 0 && cy < fractalEngine.getPixelHeight()) {
				fractalEngine.getSupersamples()[cx][cy].setColor(colorProvider.getColor(p.getColor()));
				fractalEngine.getSupersamples()[cx][cy].augmentFrequency();
			}
			augmentProgress();
		}
	}
	public ColorPoint f(FlameModel flameModel, double n, double x, double y)
	{
		double tempx = 0;
		double tempy = 0;
		int choice = 0;
		Function function = null;
		double lowerSum = 0;
		double higherSum = 0;
		for(int i = 0 ; i < flameModel.getFunctions().size(); i++) {
			higherSum = lowerSum + flameModel.getFunctions().get(i).getNormalizedProbability();
			if(n >= lowerSum && n < higherSum)
			{
				choice = i;
				function = flameModel.getFunctions().get(choice);
				break;
			}
			lowerSum = higherSum;
		}
		AffineTransform affineTransform = function.getAffineTransform();
		tempx = affineTransform.getA() * x + affineTransform.getB() * y + affineTransform.getC();
		tempy = affineTransform.getD() * x + affineTransform.getE() * y + affineTransform.getF();
		Point p = v(flameModel, choice, tempx, tempy);
		
		int tempColor = function.getColor();
		
		colorObject = (colorObject + tempColor) / 2;
		
		ColorPoint cp = new ColorPoint(p, colorObject);
		
		return cp;
	}
	public Point v(FlameModel flameModel, int n, double tempx, double tempy)
	{
		double sumx = 0.0, sumy = 0.0;
		double wtotal = 0;
		
		for(Variation v : flameModel.getFunctions().get(n).getVariations()) {
			if(v.getWeight() > 0) {
				wtotal += v.getWeight();
			}
		}
		
		for(Variation v : flameModel.getFunctions().get(n).getVariations()) {
			if(v.getWeight() > 0) {
				Point p = v.calculate(tempx, tempy);
				sumx += v.getWeight() / wtotal * p.getX();
				sumy += v.getWeight() / wtotal * p.getY();
			}
		}
		
		return new Point(sumx, sumy);
	}

	@Override
	public double getProgressTotal() {
		if(progressTotal == 0){
			progressTotal = Math.pow(10, fractalEngine.getFlameModel().getQuality()) + BEGINNING_SKIP_COUNT;
		}
		return progressTotal;
	}
	
}
